package com.googlecode.cchlib.jdbf;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

/**
 * Class for reading the metadata assuming that the given
 * {@link InputStream} carries DBF data.
 */
class DBFHeader
{
    private static final byte SIG_DBASE_III = (byte)0x03;

    /* DBF structure start here */
    private byte    signature;              /* 0 */
    byte            year;                   /* 1 */
    byte            month;                  /* 2 */
    byte            day;                    /* 3 */
    int             numberOfRecords;        /* 4-7 */
    short           headerLength;           /* 8-9 */
    short           recordLength;           /* 10-11 */
    private short   reserv1;                /* 12-13 */
    private byte    incompleteTransaction;  /* 14 */
    private byte    encryptionFlag;         /* 15 */
    private int     freeRecordThread;       /* 16-19 */
    private int     reserv2;                /* 20-23 */
    private int     reserv3;                /* 24-27 */
    private byte    mdxFlag;                /* 28 */
    private byte    languageDriver;         /* 29 */
    private short   reserv4;                /* 30-31 */
    DBFField[]      fieldArray;             /* each 32 bytes */
    private byte    terminator1;            /* n+1 */
    //byte[] databaseContainer; /* 263 bytes */
    /* DBF structure ends here */

    DBFHeader()
    {
        this.signature = SIG_DBASE_III;
        this.terminator1 = 0x0D;
    }

    void read( DataInput dataInput) throws IOException
    {
        signature = dataInput.readByte(); /* 0 */
        year = dataInput.readByte();      /* 1 */
        month = dataInput.readByte();     /* 2 */
        day = dataInput.readByte();       /* 3 */
        numberOfRecords = Utils.readLittleEndianInt( dataInput); /* 4-7 */

        headerLength = Utils.readLittleEndianShort( dataInput); /* 8-9 */
        recordLength = Utils.readLittleEndianShort( dataInput); /* 10-11 */

        reserv1 = Utils.readLittleEndianShort( dataInput);      /* 12-13 */
        incompleteTransaction = dataInput.readByte();           /* 14 */
        encryptionFlag = dataInput.readByte();                  /* 15 */
        freeRecordThread = Utils.readLittleEndianInt( dataInput); /* 16-19 */
        reserv2 = dataInput.readInt();                            /* 20-23 */
        reserv3 = dataInput.readInt();                            /* 24-27 */
        mdxFlag = dataInput.readByte();                           /* 28 */
        languageDriver = dataInput.readByte();                    /* 29 */
        reserv4 = Utils.readLittleEndianShort( dataInput);        /* 30-31 */

        List<DBFField>  v_fields = new ArrayList<DBFField>();
        DBFField        field    = DBFField.createField( dataInput); /* 32 each */

        while( field != null) {
            v_fields.add( field );
            field = DBFField.createField( dataInput );
        }

        fieldArray = new DBFField[ v_fields.size()];

        for( int i=0; i<fieldArray.length; i++) {
            fieldArray[ i ] = v_fields.get( i );
            }
     }

    void write( DataOutput dataOutput) throws IOException
    {
        dataOutput.writeByte( signature);                       /* 0 */

        GregorianCalendar calendar = new GregorianCalendar();
        year = (byte)( calendar.get( Calendar.YEAR) - 1900);
        month = (byte)( calendar.get( Calendar.MONTH)+1);
        day = (byte)( calendar.get( Calendar.DAY_OF_MONTH));

        dataOutput.writeByte( year);  /* 1 */
        dataOutput.writeByte( month); /* 2 */
        dataOutput.writeByte( day);   /* 3 */

        numberOfRecords = Utils.littleEndian( numberOfRecords);
        dataOutput.writeInt( numberOfRecords); /* 4-7 */

        headerLength = findHeaderLength();
        dataOutput.writeShort( Utils.littleEndian( headerLength)); /* 8-9 */

        recordLength = findRecordLength();
        dataOutput.writeShort( Utils.littleEndian( recordLength)); /* 10-11 */

        dataOutput.writeShort( Utils.littleEndian( reserv1)); /* 12-13 */
        dataOutput.writeByte( incompleteTransaction); /* 14 */
        dataOutput.writeByte( encryptionFlag); /* 15 */
        dataOutput.writeInt( Utils.littleEndian( freeRecordThread));/* 16-19 */
        dataOutput.writeInt( Utils.littleEndian( reserv2)); /* 20-23 */
        dataOutput.writeInt( Utils.littleEndian( reserv3)); /* 24-27 */

        dataOutput.writeByte( mdxFlag); /* 28 */
        dataOutput.writeByte( languageDriver); /* 29 */
        dataOutput.writeShort( Utils.littleEndian( reserv4)); /* 30-31 */

        for( int i=0; i<fieldArray.length; i++) {
            fieldArray[i].write( dataOutput );
            }

        dataOutput.writeByte( terminator1 ); /* n+1 */
    }

    private short findHeaderLength()
    {
        return (short)(
        1+
        3+
        4+
        2+
        2+
        2+
        1+
        1+
        4+
        4+
        4+
        1+
        1+
        2+
        (32*fieldArray.length)+
        1
        );
    }

    private short findRecordLength()
    {
        int recordLength = 0;

        for( int i=0; i<fieldArray.length; i++) {
            recordLength += fieldArray[i].getFieldLength();
            }

        return (short)(recordLength + 1);
    }
}
